// --------------------------------------------------------------------------------------------------------------------
// <copyright file="StorageAdapter.cs" company="">
//   
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace ExecutionTool.ImportExport
{
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    using ExecutionTool.Common;
    using ExecutionTool.Profile;

    using Microsoft.Practices.EnterpriseLibrary.Logging;
    using Microsoft.Practices.ServiceLocation;
    using Microsoft.WindowsAzure;
    using Microsoft.WindowsAzure.StorageClient;

    public class StorageAdapter : IStorageAdapter
    {
        #region Fields

        private readonly LogWriter logger;

        #endregion

        #region Constructors and Destructors

        public StorageAdapter(LogWriter logger = null)
        {
            this.logger = logger ?? ServiceLocator.Current.GetInstance<LogWriter>();
        }

        #endregion

        #region Public Methods and Operators

        public bool BlobExists(AzureStorageLocation targetAzureStorage, string blobAddress)
        {
            this.logger.LogVerbose(
                string.Format(
                    "Checking whether blob [{0}] on [{1}] exists...", blobAddress, BuildBlobServiceEndpoint(targetAzureStorage.StorageAccount)));

            var reference = this.GetBlobReference(targetAzureStorage, blobAddress);
            try
            {
                reference.FetchAttributes();
                this.logger.LogVerbose(
                    string.Format(
                        "Checked that blob [{0}] on [{1}] exists.", blobAddress, BuildBlobServiceEndpoint(targetAzureStorage.StorageAccount)));
                return true;
            }
            catch (StorageClientException e)
            {
                if (e.ErrorCode == StorageErrorCode.ResourceNotFound)
                {
                    this.logger.LogVerbose(
                        string.Format(
                            "Checked that blob [{0}] on [{1}] doesn't exist.", 
                            blobAddress, 
                            BuildBlobServiceEndpoint(targetAzureStorage.StorageAccount)));
                    return false;
                }

                throw;
            }
        }

        public Task CreateContainerIfNotExistAsync(AzureStorageLocation targetAzureStorage, string containerName)
        {
            this.logger.LogVerbose(
                string.Format(
                    "Creating container if not exist [{0}] on [{1}]...", containerName, BuildBlobServiceEndpoint(targetAzureStorage.StorageAccount)));

            // Create service client for credentialed access to the Blob service.
            var blobClient = new CloudBlobClient(
                BuildBlobServiceEndpoint(targetAzureStorage.StorageAccount), 
                new StorageCredentialsAccountAndKey(targetAzureStorage.StorageAccount, targetAzureStorage.AccessKey));

            // Get a reference to a container, which may or may not exist.
            var container = blobClient.GetContainerReference(containerName);

            // Create a new container, if it does not exist
            var task = Task<bool>.Factory.FromAsync(container.BeginCreateIfNotExist, container.EndCreateIfNotExist, null);
            return task;
        }

        public CloudBlob GetBlobReference(AzureStorageLocation targetAzureStorage, string blobAddress)
        {
            this.logger.LogVerbose(
                string.Format("Getting blob reference to [{0}] on [{1}]...", blobAddress, BuildBlobServiceEndpoint(targetAzureStorage.StorageAccount)));

            var blobClient = new CloudBlobClient(
                BuildBlobServiceEndpoint(targetAzureStorage.StorageAccount), 
                new StorageCredentialsAccountAndKey(targetAzureStorage.StorageAccount, targetAzureStorage.AccessKey));

            var reference = blobClient.GetBlobReference(blobAddress);

            this.logger.LogVerbose(
                string.Format("Got blob reference to [{0}] on [{1}].", blobAddress, BuildBlobServiceEndpoint(targetAzureStorage.StorageAccount)));

            return reference;
        }

        public double GetFinalBacpacSizeInBytes(AzureStorageLocation targetAzureStorage, string blobAddress)
        {
            this.logger.LogVerbose(
                string.Format("Getting size of blob [{0}] on [{1}]...", blobAddress, BuildBlobServiceEndpoint(targetAzureStorage.StorageAccount)));

            var reference = this.GetBlobReference(targetAzureStorage, blobAddress);
            reference.FetchAttributes();
            var size = reference.Properties.Length;

            this.logger.LogVerbose(
                string.Format(
                    "Got size [{2}B] of blob [{0}] on [{1}].", blobAddress, BuildBlobServiceEndpoint(targetAzureStorage.StorageAccount), size));

            return size;
        }

        public Dictionary<string, string> GetMetadata(AzureStorageLocation targetAzureStorage, string blobAddress)
        {
            this.logger.LogVerbose(
                string.Format("Getting metadata for blob [{0}] on [{1}]...", blobAddress, BuildBlobServiceEndpoint(targetAzureStorage.StorageAccount)));

            var reference = this.GetBlobReference(targetAzureStorage, blobAddress);
            reference.FetchAttributes();
            var builder = new StringBuilder();
            var dict = new Dictionary<string, string>(reference.Metadata.Count);
            for (var i = 0; i < reference.Metadata.Count; i++)
            {
                var key = reference.Metadata.Keys[i];
                var value = reference.Metadata[i];
                dict.Add(key, value);
                builder.Append(string.Format("{0}:{1};", key, value));
            }

            this.logger.LogVerbose(
                string.Format(
                    "Got metadata [{2}] for blob [{0}] on [{1}].", blobAddress, BuildBlobServiceEndpoint(targetAzureStorage.StorageAccount), builder));

            return dict;
        }

        public IEnumerable<CloudBlob> ListBlobs(AzureStorageLocation targetAzureStorage, string containerName)
        {
            var blobClient = new CloudBlobClient(
                BuildBlobServiceEndpoint(targetAzureStorage.StorageAccount), 
                new StorageCredentialsAccountAndKey(targetAzureStorage.StorageAccount, targetAzureStorage.AccessKey));
            var reference = blobClient.GetContainerReference(containerName);

            this.logger.LogVerbose(string.Format("Listing files in container [{0}]...", reference.Uri));

            var options = new BlobRequestOptions { UseFlatBlobListing = true, BlobListingDetails = BlobListingDetails.None };
            var blobs = reference.ListBlobs(options);
            var results = blobs.OfType<CloudBlob>().ToList();

            this.logger.LogVerbose(string.Format("Listed [{1}] files in folder [{0}]...", reference.Uri, results.Count));

            return results;
        }

        public void SetMetadata(AzureStorageLocation targetAzureStorage, string blobAddress, Dictionary<string, string> metadata)
        {
            this.logger.LogVerbose(
                string.Format("Setting metadata for blob [{0}] on [{1}]...", blobAddress, BuildBlobServiceEndpoint(targetAzureStorage.StorageAccount)));

            var blobClient = new CloudBlobClient(
                BuildBlobServiceEndpoint(targetAzureStorage.StorageAccount), 
                new StorageCredentialsAccountAndKey(targetAzureStorage.StorageAccount, targetAzureStorage.AccessKey));

            var reference = blobClient.GetBlobReference(blobAddress);
            var builder = new StringBuilder();
            foreach (var pair in metadata)
            {
                reference.Metadata[pair.Key] = pair.Value;
                builder.Append(string.Format("{0}:{1};", pair.Key, pair.Value));
            }

            reference.SetMetadata();

            this.logger.LogVerbose(
                string.Format(
                    "Set metadata [{2}] for blob [{0}] on [{1}].", blobAddress, BuildBlobServiceEndpoint(targetAzureStorage.StorageAccount), builder));
        }

        public void TestConnection(AzureStorageLocation targetAzureStorage)
        {
            this.logger.LogVerbose(
                string.Format("Testing connnection to blob storage [{0}]...", BuildBlobServiceEndpoint(targetAzureStorage.StorageAccount)));

            var blobClient = new CloudBlobClient(
                BuildBlobServiceEndpoint(targetAzureStorage.StorageAccount), 
                new StorageCredentialsAccountAndKey(targetAzureStorage.StorageAccount, targetAzureStorage.AccessKey));

            blobClient.GetServiceProperties();

            this.logger.LogVerbose(
                string.Format("Tested connnection to blob storage [{0}].", BuildBlobServiceEndpoint(targetAzureStorage.StorageAccount)));
        }

        #endregion

        #region Methods

        private static string BuildBlobServiceEndpoint(string storageAccount)
        {
            return string.Format("https://{0}.blob.core.windows.net", storageAccount);
        }

        #endregion
    }
}
